#include <std.h>
#include <pio.h>
#include <system.h>
#include <error.h>
#include <dint.h>

static kdrv *disk,*pt;

#define u32 unsigned long
#define u16 unsigned short
#define u8 unsigned char

#define MAXREADLEN 0x80000-0x1000


static u32 FATVOL;

//FAT32 code-------------------------------------------------------------------------------------:

static struct BPB_struct	{
	u8 jmpBoot;
	u16 jmpoff;
	u8 OEMName[8];
	u16 BytsPerSec;
	u8 SecPerClus; //Sector per cluster
	u16 RsvdSecCnt;		//Reserved sector count
	u8 NumFATs;		//No. of FAT structures
	u16 RootEntCnt;
	u16 TotSec16;
	u8 Media;
	u16 FATSz16;
	u16 SecPerTrk;
	u16 NumHeads;
	u32 HiddSec;
	u32 TotSec32;
	//FAT32 specific part:
	u32 FATSz32;
	u16 ExtFlags;
	u16 FSVer;
	u32 RootClus;			//First cluster of the root directory
	u16 FSInfo;
	u16 BkBootSec;
	u8 Reserved[12];
	u8 DrvNum;
	u8 Reserved1;
	u8 BootSig;
	u32 VolID;
	u8 VolLab[11];
	u8 FilSysType[8];
}__attribute__((packed))BPB,*BPBptr;

static struct FSI_struct	{
	u32 LeadSig;
	u8 Reserved1[480];
	u32 StrucSig;
	u32 Free_Count;
	u32 Nxt_Free;
	u8 Reserved2[12];
	u32 TrailSig;
}__attribute__((packed))FSI,*FSIptr;

static struct DIR_struct	{
	u8 Name[11];
	u8 Attr;
	u8 NTRes;		//Reserved by NT
	u8 CrtTimeTenth;  //File Creation tenth of a sec
	u16 CrtTime;		//File creation time
	u16 CrtDate;
	u16 LstAccDate;
	u16 FstClusHI;		//High of start cluster
	u16 WrtTime;
	u16 WrtDate;
	u16 FstClusLO;		//Low of start cluster
	u32 FileSize;
}__attribute__((packed))DIR,*DIR1;


static struct LDIR_struct	{
	u8 Ord;
	u8 Name1[10];
	u8 Attr;
	u8 Type;
	u8 Chksum;
	u8 Name2[12];
	u16 FstClusLO;
	u8 Name3[4];
}__attribute__((packed))LDIR,*LDIRptr;

#define sBPB_media 0xf8

#define RootDirSectors 0
#define FATSz (BPB.FATSz32)
#define FirstDataSector (BPB.RsvdSecCnt + (BPB.NumFATs * FATSz) + RootDirSectors)
#define FirstSectorofCluster(N) (((N - 2) * BPB.SecPerClus) + FirstDataSector)
#define FATOffset(N) (N*4)
#define ThisFATSecNum(N) (BPB.RsvdSecCnt + (FATOffset(N) / BPB.BytsPerSec))
#define ThisFATEntOffset(N) (FATOffset(N) % BPB.BytsPerSec)

#define sFSI_LeadSig 0x41615252
#define sFSI_StrucSig 0x61417272
#define sFSI_Free_Count_unknown 0xffffffff
#define sFSI_TrailSig 0xAA550000

#define ATTR_READ_ONLY 	0x01
#define ATTR_HIDDEN 	0x02
#define ATTR_SYSTEM 	0x04
#define ATTR_VOLUME_ID 	0x08
#define ATTR_DIRECTORY 	0x10
#define ATTR_ARCHIVE 	0x20
#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)
#define ATTR_LONG_NAME_MASK ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE

//DIR.Name[0]:
#define DIR_ENTRY_FREE 0xe5
#define DIR_ENTRY_FREE_AFTER_THIS 0x0
#define DIR_KANJI 0x5


static void read_bpb()
{
	char *buf;
	printdev("\nReading BPB:");
	buf=(char *)get_mem(512);
	disk->read_block(buf,FATVOL,1);
	BPBptr=(struct BPB_struct *)buf;
	copy(&BPB,BPBptr,struct BPB_struct);
	printdev("\nOEM:%s",BPB.OEMName);
	free(buf);
}

static void read_fsinfo()
{
	char *buf;
	printdev("\nReading FSInfo:");
	buf=(char *)get_mem(512);
	disk->read_block(buf,63+BPB.FSInfo,1);
	FSIptr=(struct FSI_struct *)buf;
	copy(&FSI,FSIptr,struct FSI_struct);
	printdev("\nLead sig :0x%x",FSI.LeadSig); 
	printdev("\nStruc sig :0x%x",FSI.StrucSig);
	printdev("\nTrail sig:0x%x",FSI.TrailSig);
	free(buf);
}

static u8 *SecBuff;
u32 read_cluster_entry(u32 cluster)
{
	u32 entry;
	u32 *e;
	disk->read_block(SecBuff,FATVOL+ThisFATSecNum(cluster),1);
	e=(u32 *)(SecBuff+ThisFATEntOffset(cluster));
	entry=(*e)&0x0fffffff;
	return entry;
}

int legal_name(struct DIR_struct *test)
{
	int i;
	for(i=0;i<11;i++)	{
		if(test->Name[i]<0x20 || test->Name[i]==0x22 || test->Name[i]==0x2a || test->Name[i]==0x2b || test->Name[i]==0x2c || test->Name[i]==0x2e || test->Name[i]==0x2f || test->Name[i]==0x3a || test->Name[i]==0x3b || test->Name[i]==0x3c || test->Name[i]==0x3d || test->Name[i]==0x3e || test->Name[i]==0x3f || test->Name[i]==0x5b || test->Name[i]==0x5c || test->Name[i]==0x5d || test->Name[i]==0x7c)
			return NULL;
	}
	return 1;
}

int read_cluster(u8 *cluster_buffer,u32 cluster)
{
	disk->read_block(cluster_buffer,FATVOL+FirstSectorofCluster(cluster),BPB.SecPerClus);
}


int get_name(char *name,struct DIR_struct *dir)
{
	int s,i=0,j=0;
	for(s=0;s<8;s++)	{
		if(dir->Name[s]==0x20)
			break;
		name[s]=dir->Name[s];
	}
	if((dir->Attr)&ATTR_DIRECTORY)	{
		name[s]=NULL;
		return NULL;
	}
	name[s++]='.';
	for(i=0;i<3;i++)	{
		if(dir->Name[8+i]!=0x20)	{
			name[s+j]=dir->Name[8+i];
			j++;
		}
	}
	name[s+j]=NULL;
	return NULL;
}

#define file_cluster(a,b) (b*0x10000+a)

u32 get_cluster(struct DIR_struct *dir)
{
	return file_cluster(dir->FstClusLO,dir->FstClusHI);
}

int read_file(u8 *ret,struct DIR_struct *dir)
{
	u8 *file_buffer=ret;
	u32 start_clus,clus,i=0,n_clus,ogclus,smclus,nmclus;
	start_mono();
	n_clus=dir->FileSize/(BPB.SecPerClus*BPB.BytsPerSec)+1;
	printdev("\nReading file...");
	ogclus=0x6f/BPB.SecPerClus;
	nmclus=1;
	smclus=get_cluster(dir);
	clus=0;
	while(clus<0x0ffffff8)	{
		start_clus=smclus;
		clus=read_cluster_entry(smclus);
		while(clus==(smclus+1) && nmclus<ogclus)	{
			nmclus++;
			smclus=clus;
			clus=read_cluster_entry(smclus);
		}
		disk->read_block(file_buffer,FATVOL+FirstSectorofCluster(start_clus),BPB.SecPerClus*nmclus);
		file_buffer+=((BPB.BytsPerSec)*(BPB.SecPerClus)*nmclus);
		nmclus=1;
	}
	print_mem(ret,100);
	release_mono();
	return NULL;
}


int valid_entry(struct DIR_struct *test)
{
	if(test->Name[0]==0xe5 || test->Name[0]==0 || test->Attr==ATTR_LONG_NAME)
		return NULL;
	return 1;
}

/*Function to search filename in the directory of the given cluster and 
put the directory structure in global 'DIR'*/

int get_entry(u32 cluster,char *filename)
{
	u32 nclus=0,clus=cluster,alloc,i=0;
	u8 *clus_buffer;
	char name[11];
	while(clus<0x0ffffff8)	{
		nclus++;
		clus=read_cluster_entry(clus);
	}
	alloc=BPB.BytsPerSec*BPB.SecPerClus*nclus;
	clus_buffer=(u8 *)get_mem(alloc);
	clus=cluster;
	while(clus<0x0ffffff8)	{
		read_cluster(clus_buffer,clus);
		clus=read_cluster_entry(clus);
	}
	while(i<(alloc/32))	{
		DIR1=(struct DIR_struct *)(clus_buffer+i*32);
		copy(&DIR,DIR1,struct DIR_struct);
		get_name(name,&DIR);
		if(valid_entry(&DIR))	{
			if(strcmp(name,filename)==NULL)	{
				free(clus_buffer);
				return SUCCESS;
			}
		}
		i++;
	}
	free(clus_buffer);
	return FAILED;		
}

static u32 current_cluster;
static char current_directory[11]; 
static char prev_directory[11];


u8 *go_entry(char *name)
{
	u8 *buf;
	if(get_entry(current_cluster,name)==NULL)	{
		printdev("\nDirectory changed.");
		if(DIR.Attr & ATTR_DIRECTORY)	{
			current_cluster=get_cluster(&DIR);
			get_name(current_directory,&DIR);
		} else	{
			buf=(u8 *)get_mem(DIR.FileSize);
			read_file(buf,&DIR);
		}
	} else	{
		printdev("\nNo such entry:%s",name);
	}
	return buf;
}

int root()
{
	current_cluster=BPB.RootClus;
	strcpy(current_directory,"root");
}

int show_dir()
{
	printdev("\nCurrent directory:%s Cluster:%d",current_directory,current_cluster);
}

int list_entries()
{
	u32 nclus=0,clus=current_cluster,alloc,i=0;
	u8 *clus_buffer;
	char name[11];
	while(clus<0x0ffffff8)	{
		nclus++;
		clus=read_cluster_entry(clus);
	}
	alloc=BPB.BytsPerSec*BPB.SecPerClus*nclus;
	clus_buffer=(u8 *)get_mem(alloc);
	clus=current_cluster;
	while(clus<0x0ffffff8)	{
		read_cluster(clus_buffer,clus);
		clus=read_cluster_entry(clus);
	}
	while(i<(alloc/32))	{
		DIR1=(struct DIR_struct *)(clus_buffer+i*32);
		copy(&DIR,DIR1,struct DIR_struct);
		get_name(name,&DIR);
		if(valid_entry(&DIR))	{
			printdev("\n%s",name);
		}
		i++;
	}
	free(clus_buffer);
	return SUCCESS;
}

#define FAT32VOL1 0x0b
#define FAT32VOL2 0x0c


kdrv *get_driver(char *);		


char *deperr="diskfmt:driver dependency error";
int init_diskfmt()
{
	u32 i;
	u8 *file;
	printdev("\nReading disk structures..");
	disk=get_driver("biosdisk&ext");
	if(disk==NULL)
		i_error(deperr);
	pt=get_driver("partitionman");
	if(pt==NULL)
		i_error(deperr);
	FATVOL=pt->read_char(FAT32VOL1);
	if(FATVOL==NULL)
		FATVOL=pt->read_char(FAT32VOL2);
	if(FATVOL==NULL)	{
		printdev("\nNo FAT32 volume found");
		return;
	}
	SecBuff=(u8 *)get_mem(512);
	read_bpb(); 
	read_fsinfo();
	root();
	return SUCCESS;
}


kdrv diskfmt={
	.name="diskformat",
	.init=init_diskfmt,
	.reset=NULL,
	.deinit=NULL,
	.select_subdevice=NULL,
	.read_char=NULL,
	.read_block=NULL,
	.write_char=NULL,
	.write_block=NULL,
};



